package com.feilong.servlet.http;

import java.io.InputStream;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.jstl.sql.Result;


import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.feilong.commons.core.Constants;
import com.feilong.commons.core.configure.PropertiesUtil;
import com.feilong.commons.core.io.IOUtil;
import com.feilong.commons.core.net.URIUtil;
import com.feilong.commons.core.util.JsonFormatUtil;
import com.feilong.commons.core.util.JsonUtil;
import com.feilong.commons.core.util.ObjectUtil;
import com.feilong.commons.core.util.StringUtil;
import com.feilong.commons.core.util.Validator;
import com.feilong.servlet.ServletContextUtil;

/**
 * HttpServletRequest工具类
 * 
 * @author <a href="mailto:venusdrogon@163.com">金鑫</a>
 * @version 1.0 2011-11-3 下午02:24:55
 */
public final class RequestUtil{

	private static final Logger	log	= LoggerFactory.getLogger(RequestUtil.class);

	private RequestUtil(){};

	/**
	 * <code>{@value}</code> <br>
	 * Standard Servlet 2.3+ spec request attributes for include URI and paths.
	 * <p>
	 * If included via a RequestDispatcher, the current resource will see the originating request. <br>
	 * Its own URI and paths are exposed as request attributes.
	 */
	public static final String	INCLUDE_REQUEST_URI_ATTRIBUTE	= "javax.servlet.include.request_uri";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	INCLUDE_CONTEXT_PATH_ATTRIBUTE	= "javax.servlet.include.context_path";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	INCLUDE_SERVLET_PATH_ATTRIBUTE	= "javax.servlet.include.servlet_path";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	INCLUDE_PATH_INFO_ATTRIBUTE		= "javax.servlet.include.path_info";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	INCLUDE_QUERY_STRING_ATTRIBUTE	= "javax.servlet.include.query_string";

	/**
	 * <code>{@value}</code><br>
	 * Standard Servlet 2.4+ spec request attributes for forward URI and paths.
	 * <p>
	 * If forwarded to via a RequestDispatcher, the current resource will see its own URI and paths. The originating URI and paths are exposed as request
	 * attributes.
	 */
	public static final String	FORWARD_REQUEST_URI_ATTRIBUTE	= "javax.servlet.forward.request_uri";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	FORWARD_CONTEXT_PATH_ATTRIBUTE	= "javax.servlet.forward.context_path";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	FORWARD_SERVLET_PATH_ATTRIBUTE	= "javax.servlet.forward.servlet_path";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	FORWARD_PATH_INFO_ATTRIBUTE		= "javax.servlet.forward.path_info";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	FORWARD_QUERY_STRING_ATTRIBUTE	= "javax.servlet.forward.query_string";

	/**
	 * <code>{@value}</code><br>
	 * Standard Servlet 2.3+ spec request attributes for error pages.
	 * <p>
	 * To be exposed to JSPs that are marked as error pages,<br>
	 * when forwarding to them directly rather than through the servlet container's error page resolution mechanism.
	 */
	public static final String	ERROR_STATUS_CODE_ATTRIBUTE		= "javax.servlet.error.status_code";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	ERROR_EXCEPTION_TYPE_ATTRIBUTE	= "javax.servlet.error.exception_type";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	ERROR_MESSAGE_ATTRIBUTE			= "javax.servlet.error.message";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	ERROR_EXCEPTION_ATTRIBUTE		= "javax.servlet.error.exception";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	ERROR_REQUEST_URI_ATTRIBUTE		= "javax.servlet.error.request_uri";

	/**
	 * <code>{@value}</code>
	 */
	public static final String	ERROR_SERVLET_NAME_ATTRIBUTE	= "javax.servlet.error.servlet_name";

	// public final static String getServerRootWithContextPath(HttpServletRequest request){
	// return "http://" + request.getServerName() + (request.getServerPort() == 80 ? "" : ":" + request.getServerPort() + request.getContextPath());
	// }
	/************************************************************************************************************/

	/**
	 * 获得request error 相关参数 map
	 * 
	 * @param request
	 *            HttpServletRequest
	 * @return 如果request 有 {@link #ERROR_STATUS_CODE_ATTRIBUTE}属性,则返回error 相关属性 封装到map,<br>
	 *         如果 request没有 {@link #ERROR_STATUS_CODE_ATTRIBUTE}属性,返回null
	 */
	public static Map<String, String> getErrorMap(HttpServletRequest request){
		String errorCode = getAttributeToString(request, ERROR_STATUS_CODE_ATTRIBUTE);
		if (Validator.isNotNullOrEmpty(errorCode)){
			Map<String, String> map = new LinkedHashMap<String, String>();
			map.put(ERROR_STATUS_CODE_ATTRIBUTE, errorCode);
			map.put(ERROR_REQUEST_URI_ATTRIBUTE, getAttributeToString(request, ERROR_REQUEST_URI_ATTRIBUTE));
			map.put(ERROR_EXCEPTION_ATTRIBUTE, getAttributeToString(request, ERROR_EXCEPTION_ATTRIBUTE));
			map.put(ERROR_EXCEPTION_TYPE_ATTRIBUTE, getAttributeToString(request, ERROR_EXCEPTION_TYPE_ATTRIBUTE));
			map.put(ERROR_MESSAGE_ATTRIBUTE, getAttributeToString(request, ERROR_MESSAGE_ATTRIBUTE));
			map.put(ERROR_SERVLET_NAME_ATTRIBUTE, getAttributeToString(request, ERROR_SERVLET_NAME_ATTRIBUTE));
			return map;
		}
		return null;
	}

	/**
	 * 遍历显示request的attribute 用于debug
	 * 
	 * @param request
	 */
	public static void showAttributes(HttpServletRequest request){
		@SuppressWarnings("unchecked")
		Enumeration<String> attributeNames = request.getAttributeNames();
		while (attributeNames.hasMoreElements()){
			String name = attributeNames.nextElement();
			String value = getAttributeToString(request, name);
			log.debug("{}:{}", name, value);
		}
	}

	/**
	 * 将request 相关 get 数据 转成 \n 分隔的 String,以便log 显示
	 * 
	 * @param request
	 * @return
	 */
	public static String getRequestStringForLog(HttpServletRequest request){
		StringBuilder stringBuilder = new StringBuilder();
		String wrap = "\n\t\t";
		stringBuilder.append(wrap + "request.getAuthType():" + request.getAuthType());
		stringBuilder.append(wrap + "request.getCharacterEncoding():" + request.getCharacterEncoding());
		stringBuilder.append(wrap + "request.getContentLength():" + request.getContentLength());
		stringBuilder.append(wrap + "request.getContextPath():" + request.getContextPath());
		stringBuilder.append(wrap + "request.getLocalAddr():" + request.getLocalAddr());
		stringBuilder.append(wrap + "request.getLocale():" + request.getLocale());
		stringBuilder.append(wrap + "request.getLocalName():" + request.getLocalName());
		stringBuilder.append(wrap + "request.getLocalPort():" + request.getLocalPort());
		stringBuilder.append(wrap + "request.getMethod():" + request.getMethod());
		stringBuilder.append(wrap + "request.getPathInfo():" + request.getPathInfo());
		stringBuilder.append(wrap + "request.getPathTranslated():" + request.getPathTranslated());
		stringBuilder.append(wrap + "request.getProtocol():" + request.getProtocol());
		stringBuilder.append(wrap + "request.getQueryString():" + request.getQueryString());
		stringBuilder.append(wrap + "request.getRemoteAddr():" + request.getRemoteAddr());
		stringBuilder.append(wrap + "request.getRemoteHost():" + request.getRemoteHost());
		stringBuilder.append(wrap + "request.getRemotePort():" + request.getRemotePort());
		stringBuilder.append(wrap + "request.getRemoteUser():" + request.getRemoteUser());
		stringBuilder.append(wrap + "request.getRequestedSessionId():" + request.getRequestedSessionId());
		stringBuilder.append(wrap + "request.getRequestURI():" + request.getRequestURI());
		stringBuilder.append(wrap + "request.getRequestURL():" + request.getRequestURL());
		stringBuilder.append(wrap + "request.getScheme():" + request.getScheme());
		stringBuilder.append(wrap + "request.getServerName():" + request.getServerName());
		stringBuilder.append(wrap + "request.getServerPort():" + request.getServerPort());
		stringBuilder.append(wrap + "request.getServletPath():" + request.getServletPath());
		return stringBuilder.toString();
	}

	/**
	 * 遍历显示request的header 用于debug
	 * 
	 * @param request
	 */
	public static void showHeaders(HttpServletRequest request){
		@SuppressWarnings("unchecked")
		Enumeration<String> headerNames = request.getHeaderNames();
		Integer allLength = 0;
		while (headerNames.hasMoreElements()){
			String name = headerNames.nextElement();
			String value = request.getHeader(name);
			Integer length = value.length();
			Object[] object = { name, value, length };
			log.debug("[{}]:{},length:[{}]", object);
			allLength = allLength + length;
		}
		log.debug("allLength:[{}]", IOUtil.convertFileSize(allLength));
	}

	/** ******************************* url参数相关 ***************************************************** */
	// [start] url参数相关
	/**
	 * 获得requst属性值,并将其转换成int类型
	 * 
	 * @param request
	 *            请求
	 * @param name
	 *            参数名
	 * @return 获得requst属性值,并将其转换成int类型
	 */
	public final static Integer getAttributeToInt(HttpServletRequest request,String name){
		Object value = getAttribute(request, name);
		return ObjectUtil.toInteger(value);
	}

	/**
	 * 取到request里面的属性值
	 * 
	 * @param request
	 *            请求
	 * @param name
	 *            属性名称
	 * @return 取到request里面的属性值
	 */
	public final static Object getAttribute(HttpServletRequest request,String name){
		return request.getAttribute(name);
	}

	/**
	 * 取到request里面的属性值
	 * 
	 * @param request
	 *            请求
	 * @param name
	 *            属性名称
	 * @return 取到request里面的属性值
	 */
	public final static String getAttributeToString(HttpServletRequest request,String name){
		Object value = request.getAttribute(name);
		return ObjectUtil.toString(value);
	}

	/**
	 * 获得请求的?部分前面的地址<br>
	 * 识别 request 是否 forword
	 * 
	 * <pre>
	 * 如:http://localhost:8080/feilong/requestdemo.jsp?id=2
	 * 返回:http://localhost:8080/feilong/requestdemo.jsp
	 * 
	 * 注:
	 * 	 request.getRequestURI() 返回值类似：/feilong/requestdemo.jsp
	 * 	 request.getRequestURL() 返回值类似：http://localhost:8080/feilong/requestdemo.jsp
	 * </pre>
	 * 
	 * @param request
	 * @return 获得请求的?部分前面的地址
	 */
	public final static String getRequestURL(HttpServletRequest request){
		String forward_request_uri = (String) request.getAttribute(FORWARD_REQUEST_URI_ATTRIBUTE);
		showAttributes(request);
		if (Validator.isNotNull(forward_request_uri)){
			return forward_request_uri;
		}
		return request.getRequestURL().toString();
	}

	// [end]
	/**
	 * 获得项目本地ip地址
	 * 
	 * @param request
	 * @return Returns the Internet Protocol (IP) address of the interface on which the request was received.
	 */
	public final static String getLocalAddr(HttpServletRequest request){
		return request.getLocalAddr();
	}

	/**
	 * 获得客户端ip地址
	 * 
	 * @param request
	 * @return 获得客户端ip地址
	 */
	public final static String getClientIp(HttpServletRequest request){
		// WL-Proxy-Client-IP=215.4.1.29
		// Proxy-Client-IP=215.4.1.29
		// X-Forwarded-For=215.4.1.29
		// WL-Proxy-Client-Keysize=
		// WL-Proxy-Client-Secretkeysize=
		// X-WebLogic-Request-ClusterInfo=true
		// X-WebLogic-KeepAliveSecs=30
		// X-WebLogic-Force-JVMID=-527489098
		// WL-Proxy-SSL=false
		String unknown = "unknown";
		/**
		 * X-Forwarded-For:简称XFF头，它代表客户端，也就是HTTP的请求端真实的IP，只有在通过了HTTP 代理或者负载均衡服务器时才会添加该项。<br>
		 * 它不是RFC中定义的标准请求头信息，在squid缓存代理服务器开发文档中可以找到该项的详细介绍。 <br>
		 * 标准格式如下：<br>
		 * X-Forwarded-For: client1, proxy1, proxy2
		 */
		String xForwardedFor = "x-forwarded-for";
		String proxyClientIP = "Proxy-Client-IP";
		/**
		 * WL-Proxy-Client-IP 这个应该是WebLogic前置HttpClusterServlet提供的属性，一般不需要自己处理，在WebLogic控制台中已经可以指定使用这个属性来覆盖
		 */
		String wLProxyClientIP = "WL-Proxy-Client-IP";
		String wrap = "\n\t\t";
		StringBuilder logBuilder = new StringBuilder();
		// 是否使用反向代理
		String ipAddress = request.getHeader(xForwardedFor);
		logBuilder.append(wrap + "header_xForwardedFor:" + ipAddress);
		if (Validator.isNull(ipAddress) || unknown.equalsIgnoreCase(ipAddress)){
			ipAddress = request.getHeader(proxyClientIP);
			logBuilder.append(wrap + "header_proxyClientIP:" + ipAddress);
		}
		if (Validator.isNull(ipAddress) || unknown.equalsIgnoreCase(ipAddress)){
			ipAddress = request.getHeader(wLProxyClientIP);
			logBuilder.append(wrap + "header_wLProxyClientIP:" + ipAddress);
		}
		if (Validator.isNull(ipAddress) || unknown.equalsIgnoreCase(ipAddress)){
			ipAddress = request.getRemoteAddr();
			logBuilder.append(wrap + "request.getRemoteAddr():" + ipAddress);
		}
		// 对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
		if (ipAddress != null && ipAddress.length() > 15){ // "***.***.***.***".length() = 15
			logBuilder.append(wrap + "all:" + ipAddress);
			if (ipAddress.indexOf(",") > 0){
				ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
			}
		}
		log.debug(logBuilder.toString());
		return ipAddress;
	}

	/**
	 * 　User Agent中文名为用户代理，简称 UA，<br>
	 * 它是一个特殊字符串头，使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。
	 * 
	 * @param request
	 * @return
	 */
	public final static String getUserAgent(HttpServletRequest request){
		String header_userAgent = "User-Agent";
		return request.getHeader(header_userAgent);
	}

	/**
	 * 获得上上个请求的URL
	 * 
	 * <pre>
	 * 请用于常规请求,必须走http协议才有值,javascript跳转无效
	 * 以下情况请慎用:
	 * 	  也就是说要通过&lt;a href=&quot;url&quot;&gt;sss&lt;/a&gt;标记才能获得那个值   
	 * 	而通过改变location或是&lt;a href=&quot;javascript:location='url'&quot;&gt;sss&lt;/a&gt;都是得不到那个值得
	 * 
	 * referer是浏览器在用户提交请求当前页面中的一个链接时,将当前页面的URL放在头域中提交给服务端的,如当前页面为a.html,
	 * 它里面有一个b.html的链接,当用户要访问b.html时浏览器就会把a.html作为referer发给服务端.
	 * 
	 * <ul><li>
	 * 有嵌入iframe
	 * </li></ul>
	 * 
	 * </pre>
	 * 
	 * @param request
	 * @return 上上个请求的URL
	 */
	public final static String getReferer(HttpServletRequest request){
		String header_referer = "referer";
		return request.getHeader(header_referer);
	}

	/**
	 * Return the servlet path for the given request, detecting an include request URL if called within a RequestDispatcher include.
	 * 
	 * @param request
	 *            current HTTP request
	 * @return the servlet path
	 */
	public static String getOriginatingServletPath(HttpServletRequest request){
		String servletPath = (String) request.getAttribute(FORWARD_SERVLET_PATH_ATTRIBUTE);
		if (servletPath == null){
			servletPath = request.getServletPath();
		}
		return servletPath;
	}

	/**
	 * 将collection转成map
	 * 
	 * <pre>
	 * 暂时仅支持 list及数组
	 * 
	 * </pre>
	 * 
	 * @param map
	 * @param scopeOptions
	 *            作用域集合名称
	 * @param scopeLabel
	 *            显示,如果为空,则默认为scopeOptions的名称去掉后缀List 自动加上Name
	 * @param scopeValue
	 *            值,如果为空,则默认为scopeOptions的名称去掉后缀List 自动加上Id
	 * @param request
	 * @return map
	 */
	@SuppressWarnings("rawtypes")
	public final static Map<String, String> collectionToMap(
			Map<String, String> map,
			String scopeOptions,
			String scopeLabel,
			String scopeValue,
			HttpServletRequest request){
		if (null == map){
			map = new LinkedHashMap<String, String>();
		}
		String s_label = "";
		String s_value = "";
		Object optionCollection = request.getAttribute(scopeOptions);
		// optionList不是null
		if (Validator.isNotNull(optionCollection)){
			// 集合
			if (optionCollection instanceof List){
				// 判断scopeOptions以List结尾
				String specialCharacters = "List";
				boolean isEndsWithListString = scopeOptions.endsWith(specialCharacters);
				// 去掉末尾
				String beanName = scopeOptions.substring(0, scopeOptions.length() - specialCharacters.length());
				// scopeValue
				if (Validator.isNull(scopeValue)){
					if (isEndsWithListString){
						scopeValue = beanName + "Id";
					}
				}
				// scopeLabel
				if (Validator.isNull(scopeLabel)){
					if (isEndsWithListString){
						scopeLabel = beanName + "Name";
					}
				}
				try{
					List list = (List) optionCollection;
					for (Object objectOption : list){
						s_label = PropertyUtils.getProperty(objectOption, scopeLabel).toString();
						s_value = PropertyUtils.getProperty(objectOption, scopeValue).toString();
						map.put(s_value, s_label);
					}
				}catch (Exception e){
					e.printStackTrace();
				}
			}
			// Result 用于jdbc
			else if (optionCollection instanceof Result){
				// 判断scopeOptions以List结尾
				String specialCharacters = "Result";
				boolean isEndsWithListString = scopeOptions.endsWith(specialCharacters);
				// 去掉末尾
				String beanName = scopeOptions.substring(0, scopeOptions.length() - specialCharacters.length());
				// scopeValue
				if (Validator.isNull(scopeValue)){
					if (isEndsWithListString){
						scopeValue = beanName + "Id";
					}
				}
				// scopeLabel
				if (Validator.isNull(scopeLabel)){
					if (isEndsWithListString){
						scopeLabel = beanName + "Name";
					}
				}
				Result result = (Result) optionCollection;
				SortedMap[] sortedMaps = result.getRows();
				for (SortedMap sortedMap : sortedMaps){
					s_label = sortedMap.get(scopeLabel).toString();
					s_value = sortedMap.get(scopeValue).toString();
					map.put(s_value, s_label);
				}
			}
			// 数组
			else if (optionCollection instanceof Object[]){}
		}
		return map;
	}

	/**
	 * 获得请求的经过8859处理后的全路径
	 * 
	 * @param request
	 *            当前请求
	 * @return 获得请求的经过8859处理后的全路径
	 */
	public final static String getRequestAllURI_8859(HttpServletRequest request){
		// 得到请求的uri(工程名+请求页面，不含?及后面的内容，也不含ip地址及端口)
		String uri = request.getRequestURI();
		// 得到?后面的内容 判断是否有?(并不是每个网页都有?
		String query = URIUtil.decodeLuanMa_ISO8859(request.getQueryString());
		if (Validator.isNotNullOrEmpty(query)){
			uri = uri + "?" + query;
		}
		return uri;
	}

	/**
	 * 获得请求的全地址
	 * 
	 * @param request
	 * @return 如:http://localhost:8080/feilong/requestdemo.jsp?id=2
	 */
	public final static String getRequestAllURL(HttpServletRequest request){
		String queryString = request.getQueryString();
		String returnValue = "";
		if (Validator.isNullOrEmpty(queryString)){
			returnValue = getRequestURL(request);
		}else{
			returnValue = getRequestURL(request) + "?" + URIUtil.decodeLuanMa_ISO8859(queryString);
		}
		return returnValue;
	}

	/**
	 * 读取文件内容
	 * 
	 * @param request
	 *            HttpServletRequest
	 * @param directoryName
	 *            文件夹路径 前面没有/ 后面有/ 如:res/html/email/
	 * @param fileName
	 *            文件名称 如:register.html
	 * @return 读取文件内容
	 */
	public final static String getFileContent(HttpServletRequest request,String directoryName,String fileName){
		ServletContext servletContext = request.getSession().getServletContext();
		return ServletContextUtil.getFileContent(servletContext, directoryName, fileName);
	}

	/**
	 * 是否是本地ip(测试)
	 * 
	 * @param request
	 * @return 是否是本地ip(测试)
	 */
	public final static boolean isLocalHost(HttpServletRequest request){
		return Constants.localhostIp.equals(getLocalAddr(request));
	}

	/**
	 * 判断是否是通过电脑web访问
	 * 
	 * @param request
	 * @return web访问返回true
	 * @deprecated 不准确 待修改
	 */
	public final static boolean isWebVisit(HttpServletRequest request){
		String userAgent = getUserAgent(request);
		if (Validator.isNotNull(userAgent) && StringUtil.isContain(userAgent.toLowerCase(), "mozilla")){
			return true;
		}
		return false;
	}

	/**
	 * 判断一个请求 是否是ajax 请求<br>
	 * 注:x-requested-with这个头是某些JS类库给加上去的，直接写AJAX是没有这个头的,<br>
	 * jquery/ext 确定添加,暂时可以使用这个来判断<br>
	 * <br>
	 * The X-Requested-With is a non-standard HTTP header which is mainly used to identify Ajax requests. <br>
	 * Most JavaScript frameworks send this header with value of XMLHttpRequest.
	 * 
	 * @param request
	 * @return 如果是ajax 请求 返回true
	 * @see http://en.wikipedia.org/wiki/X-Requested-With#Requested-With
	 */
	public final static boolean isAjaxRequest(HttpServletRequest request){
		String header = request.getHeader("X-Requested-With");
		if (Validator.isNotNull(header) && header.equalsIgnoreCase("XMLHttpRequest")){
			return true;
		}
		return false;
	}

	/**
	 * 获取Properties配置文件键值
	 * 
	 * @param servletContext
	 *            servletContext
	 * @param propertiesPath
	 *            Properties文件路径 如"/WEB-INF/classes/feilong.user.properties"
	 * @param key
	 *            键
	 * @return 获取Properties配置文件键值
	 * @deprecated 这个方法使用场景 几乎没有,可以使用spring 代替
	 */
	public final static String getPropertiesValue(ServletContext servletContext,String propertiesPath,String key){
		InputStream inputStream = servletContext.getResourceAsStream(propertiesPath);
		Properties properties = PropertiesUtil.getProperties(inputStream);
		return properties.getProperty(key);
	}

}
